/*
 * HexHelper.java
 *
 * Created on December 24, 2009, 11:23 PM
 */

package torchtools;

import java.awt.Component;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Arrays;
import java.util.prefs.Preferences;
import javax.swing.ButtonGroup;
import javax.swing.JOptionPane;
import javax.swing.UIManager;

/**
 *
 * @author  Matt
 */
public class HexHelper extends javax.swing.JFrame {
    
    Preferences prefs = Preferences.userNodeForPackage(HexHelper.class);
    public static final String TYPE_TEXT = "text";
    public static final String TYPE_INT = "int";
    
    private KeyListener keyListener = new KeyAdapter() {
        public void keyPressed(KeyEvent e) {
            //if we're pressing alt...
            if ((e.getModifiers() & KeyEvent.ALT_MASK) == KeyEvent.ALT_MASK) {
                int key = e.getKeyCode();
                if (key==KeyEvent.VK_1)
                    hexField.grabFocus();
                else if (key==KeyEvent.VK_2)
                    valueField.grabFocus();
            }
        }
    };
    
    /** Creates new form HexHelper */
    public HexHelper() {
        initComponents();
        setLocationRelativeTo(null);
        ButtonGroup grp = new ButtonGroup();
        grp.add(typeText);
        grp.add(typeInt);
        
        unicodeStrings.setMnemonic('U');
        includeSpaces.setMnemonic('S');
        reverseBytes.setMnemonic('R');
        typeText.setMnemonic('T');
        typeInt.setMnemonic('I');
        
        Component[] comps = new Component[] {
            this, getContentPane(), typeText, typeInt, hexField,
            valueField, helpButton, unicodeStrings, includeSpaces,
            reverseBytes
        };
        for (Component c : comps)
            c.addKeyListener(keyListener);
        
        String type = prefs.get("type", TYPE_INT);
        unicodeStrings.setSelected(prefs.getBoolean("unicodeStrings", true));
        includeSpaces.setSelected(prefs.getBoolean("includeSpaces", true));
        reverseBytes.setSelected(prefs.getBoolean("reverseBytes", true));
        if (type.equals(TYPE_TEXT))
            typeText.doClick();
        else if (type.equals(TYPE_INT))
            typeInt.doClick();
        hexField.grabFocus();
    }
    
    private void cleanup() {
        String type = TYPE_INT;
        if (typeText.isSelected())
            type = TYPE_TEXT;
        prefs.put("type", type);
        prefs.putBoolean("unicodeStrings", unicodeStrings.isSelected());
        prefs.putBoolean("includeSpaces", includeSpaces.isSelected());
        prefs.putBoolean("reverseBytes", reverseBytes.isSelected());
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        hexField = new javax.swing.JTextField();
        hexLabel = new javax.swing.JLabel();
        valueField = new javax.swing.JTextField();
        valueLabel = new javax.swing.JLabel();
        typeInt = new javax.swing.JToggleButton();
        typeText = new javax.swing.JToggleButton();
        jLabel3 = new javax.swing.JLabel();
        reverseBytes = new javax.swing.JCheckBox();
        unicodeStrings = new javax.swing.JCheckBox();
        includeSpaces = new javax.swing.JCheckBox();
        helpButton = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("HexHelper");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        hexField.setToolTipText("Enter hexidecimals to be converted");
        hexField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                hexFieldActionPerformed(evt);
            }
        });
        hexField.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                hexFieldFocusGained(evt);
            }
        });

        hexLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
        hexLabel.setText("Hex:");

        valueField.setToolTipText("Enter text or int values to be converted");
        valueField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                valueFieldActionPerformed(evt);
            }
        });
        valueField.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                valueFieldFocusGained(evt);
            }
        });

        valueLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
        valueLabel.setText("Value:");

        typeInt.setSelected(true);
        typeInt.setText("dec");
        typeInt.setToolTipText("Convert to integers");
        typeInt.setRequestFocusEnabled(false);

        typeText.setText("text");
        typeText.setToolTipText("Convert to text-strings");
        typeText.setRequestFocusEnabled(false);

        jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
        jLabel3.setText("Type:");

        reverseBytes.setSelected(true);
        reverseBytes.setText("Reverse int bytes");
        reverseBytes.setToolTipText("");
        reverseBytes.setRequestFocusEnabled(false);

        unicodeStrings.setSelected(true);
        unicodeStrings.setText("Unicode strings");
        unicodeStrings.setRequestFocusEnabled(false);

        includeSpaces.setSelected(true);
        includeSpaces.setText("Include spaces");
        includeSpaces.setRequestFocusEnabled(false);

        helpButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/res/help.png"))); // NOI18N
        helpButton.setToolTipText("Help/About");
        helpButton.setContentAreaFilled(false);
        helpButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        helpButton.setRequestFocusEnabled(false);
        helpButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                helpButtonActionPerformed(evt);
            }
        });

        jButton2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/res/warp.png"))); // NOI18N
        jButton2.setToolTipText("Convert to value");
        jButton2.setContentAreaFilled(false);
        jButton2.setFocusable(false);
        jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jButton2.setRequestFocusEnabled(false);
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                hexFieldActionPerformed(evt);
            }
        });

        jButton3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/res/warp.png"))); // NOI18N
        jButton3.setToolTipText("Convert to hex");
        jButton3.setContentAreaFilled(false);
        jButton3.setFocusable(false);
        jButton3.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jButton3.setRequestFocusEnabled(false);
        jButton3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                valueFieldActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
                            .add(jLabel3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(hexLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(valueLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                            .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup()
                                .add(typeText)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(typeInt)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 153, Short.MAX_VALUE)
                                .add(helpButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 16, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                            .add(layout.createSequentialGroup()
                                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                                    .add(org.jdesktop.layout.GroupLayout.LEADING, valueField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 255, Short.MAX_VALUE)
                                    .add(hexField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 255, Short.MAX_VALUE))
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                                    .add(jButton2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 16, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                                    .add(jButton3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 16, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))))
                    .add(org.jdesktop.layout.GroupLayout.LEADING, reverseBytes)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false)
                        .add(org.jdesktop.layout.GroupLayout.LEADING, includeSpaces, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .add(org.jdesktop.layout.GroupLayout.LEADING, unicodeStrings, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel3)
                    .add(typeText, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 25, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .add(typeInt, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 25, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .add(helpButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 23, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(hexLabel)
                    .add(jButton2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 23, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .add(hexField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(valueLabel)
                    .add(valueField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .add(jButton3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 23, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(reverseBytes, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 23, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(unicodeStrings)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(includeSpaces)
                .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void hexFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hexFieldActionPerformed
        String hex = hexField.getText();
        String value = valueField.getText();
        try {
            if (typeInt.isSelected()) {
                long i = hex2dec(hex, reverseBytes.isSelected());
                value = Long.toString(i);
            } else if (typeText.isSelected()) {
                String text = hex2text(hex);
                value = text;
            }
        } catch (Exception e) {
            value = "Error -- invalid input";
        }
        valueField.setText(value);
    }//GEN-LAST:event_hexFieldActionPerformed

    private void valueFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_valueFieldActionPerformed
        String value = valueField.getText();
        String hex = hexField.getText();
        try {
            if (typeInt.isSelected()) {
                hex = dec2hex(Long.parseLong(value), reverseBytes.isSelected(),
                            includeSpaces.isSelected());
            } else if (typeText.isSelected()) {
                hex = text2hex(value);
            }
        } catch (Exception e) {
            hex = "Error - invalid input";
        }
        hexField.setText(hex);
    }//GEN-LAST:event_valueFieldActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        cleanup();
    }//GEN-LAST:event_formWindowClosing

    private void hexFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_hexFieldFocusGained
        hexField.setSelectionStart(0);
        hexField.setSelectionEnd(hexField.getText().length());
    }//GEN-LAST:event_hexFieldFocusGained
    
    private void valueFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_valueFieldFocusGained
        valueField.setSelectionStart(0);
        valueField.setSelectionEnd(valueField.getText().length());
    }//GEN-LAST:event_valueFieldFocusGained

    private void helpButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_helpButtonActionPerformed
        String msg = "<html>HexHelper lets you convert hexidecimal to " +
                "string-text and integers.<br>To use it, select the type you " +
                "wish to convert to, type your data<br>into the appropriate field " +
                "and press <b>Enter</b> or click the <b>Convert To</b><br> button " +
                "on the right.<br><br>Created by mattdesl.</html>";
        JOptionPane.showMessageDialog(this, msg, "About", JOptionPane.PLAIN_MESSAGE);
}//GEN-LAST:event_helpButtonActionPerformed
    
    
    public static long hex2dec(String s, boolean reverse) {
        String digits = "0123456789ABCDEF";
        s = s.replace(" ", "").toUpperCase();
        if (s.length()%2!=0)
            s = '0'+s;
        if (reverse) {
            String rev = "";
            for (int i=0; i<s.length()-1; i+=2) {
                rev = s.substring(i, i+2) + rev;
            }
            s = rev;
        }
        long val = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            int d = digits.indexOf(c);
            val = 16*val + d;
        }
        return val;
    }
    
    // precondition:  d is a nonnegative integer
    public static String dec2hex(long d, boolean reverse, boolean spaces) {
        String hex = Long.toHexString(d).toUpperCase();
        
        /*
        String digits = "0123456789ABCDEF";
        if (d == 0) return "0";
        String hex = "";
        while (d > 0) {
            int digit = (int)(d % 16);         // rightmost digit
            hex = digits.charAt(digit) + hex;  // string concatenation
            d = d / 16;
        }*/
        if (hex.length()%2!=0)
            hex = '0'+hex;
        String s = spaces ? " " : "";
        String rev = "";
        for (int i=0; i<hex.length()-1; i+=2) {
            if (reverse)
                rev = hex.substring(i, i+2) + s + rev;
            else 
                rev = rev + hex.substring(i, i+2) + s;
        }
        return rev.trim();
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        //System.out.println(dec2hex(3195030212259540514L, true, true));
        
        try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
        catch (Exception e) {}
        
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new HexHelper().setVisible(true);
            }
        });
    }
    /*
    private int hex2int(String hex) {
        hex = hex.replace(" ", "");
        int len = hex.length();
        if (reverseBytes.isSelected()) {
            for (int i=0; i<8-len; i++) {
                hex += '0';
            }
        }
        int i = Integer.parseInt(hex, 16);
        if (reverseBytes.isSelected())
            i = Integer.reverseBytes(i);
        return i;
    }
    
    //from hxd 17 CE 02 00
    //reversed 00 02 ce 17 -> 183,831 gp
    
    //adds spaces, adds a zero at the beginning if necessary
    private String fixHex(String s) {
        if (s.length()%2!=0)
            s = '0'+s;
        StringBuffer buf = new StringBuffer();
        for (int x=0; x<s.length()-1; x+=2) {
            buf.append(s.charAt(x));
            buf.append(s.charAt(x+1));
            if (includeSpaces.isSelected())
                buf.append(' ');
        }
        return buf.toString().trim().toUpperCase();
    }
    
    private String int2hex(int i) {
        if (reverseBytes.isSelected())
            i = Integer.reverseBytes(i);
        return fixHex(Integer.toHexString(i));        
    }*/
    
    private String hex2text(String hex) {
        hex = hex.replace(" ", "");
        if (hex.length()%2!=0)
            hex = '0'+hex;
        StringBuffer buf = new StringBuffer(/* TODO: initial capacity */);
        for (int x=0, j=0; x<hex.length()-1; x+=2, j++) {
            //if unicode strings is enabled, ignore odd indices
            if (!unicodeStrings.isSelected() || j%2==0) {
                //get next pair, convert to int w/ radix 16
                char c = (char)Integer.parseInt(hex.substring(x, x+2), 16);
                buf.append(c);
            }
        }
        return buf.toString();
    }
    
    private String text2hex(String text) {
        String chs = unicodeStrings.isSelected() ? "UTF-16LE" : "UTF-8";
        byte[] bytes;
        try { bytes = text.getBytes(chs); }
        catch (Exception e) { bytes = text.getBytes(); System.err.println("err"); }
        String result = "";
        for (int i=0; i < bytes.length; i++) {
            if (bytes[i] == (byte)0x00 && !unicodeStrings.isSelected()) {
                result += "00";
            } else {
                result += Integer.toString( ( bytes[i] & 0xff ) 
                                + 0x100, 16).substring( 1 );
            }
            if (includeSpaces.isSelected())
                result += ' ';
        }
        return result.toUpperCase();

    }
        
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton helpButton;
    private javax.swing.JTextField hexField;
    private javax.swing.JLabel hexLabel;
    private javax.swing.JCheckBox includeSpaces;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JCheckBox reverseBytes;
    private javax.swing.JToggleButton typeInt;
    private javax.swing.JToggleButton typeText;
    private javax.swing.JCheckBox unicodeStrings;
    private javax.swing.JTextField valueField;
    private javax.swing.JLabel valueLabel;
    // End of variables declaration//GEN-END:variables
    
}
